子查询:先把答案查出来,再拿这个答案继续查

这一页围绕 父查询、子查询、执行顺序、单值与多值、相关子查询与 EXISTS 这几个核心逻辑展开,重点放在先理解查询过程,再理解语句写法。

学习这一节后,至少要掌握三件事
  • 能分清普通子查询和相关子查询
  • 能根据子查询返回一个值还是一组值,选对 =、IN、ANY、ALL
  • 能读懂 EXISTS / NOT EXISTS 这类逐行判断的写法
2 类
普通子查询 / 相关子查询
3 组
单值 / 多值 / EXISTS 场景
11 例
覆盖 PPT 中例 6-40 到 6-50、6-51 到 6-55
🎯 假设你要查“比赵琳琳年龄大的学生”,这时数据库并不知道“赵琳琳几岁”。所以必须先查出赵琳琳的年龄,再拿这个年龄去筛选别人。 这种“先查一个小答案,再用这个小答案完成大查询”的方式,就是子查询。

一、先把子查询的骨架看懂

📘 什么是子查询
把一个查询语句写到另一个查询语句内部,里面的查询叫 子查询,外面的查询叫 父查询
💬 人话翻译
父查询像主任务,子查询像先去打听消息的小助手。小助手把结果带回来,父查询再决定最后要哪些记录。
✅ 两个最关键的判断
第一,看子查询有没有用到父查询当前行的数据。
第二,看子查询返回的是一个值,还是一组值。
判断点结论
不依赖父查询当前行普通子查询
依赖父查询当前行相关子查询
返回一个值常配 =、>、<>
返回一组值常配 IN、ANY、ALL

普通子查询和相关子查询,执行顺序完全不同

普通子查询
1
先执行子查询
2
得到一个值或一组值
3
父查询拿结果比较

通常可以理解为:子查询先跑完,再交给外层使用。

相关子查询
1
父查询先取一行
2
子查询读取这行中的值
3
逐行重复判断

可以理解为:父查询每走一行,子查询就要重新问一次。

常见表名快速回忆

学生表 s
snosnagemaj
s1张三18计算机
s2赵琳琳19数学
教师表 t
tnotnprofsal
t1顾伟副教授5200
t2李明讲师4300
课程/选课/授课表
关键字段
ccno, cn, ct
scsno, cno, score
tctno, cno, tcdate

二、返回一个值的普通子查询

一句话记忆
子查询只返回一个结果时,父查询通常把这个结果当成一个普通常量来比较。

查询比学生“赵琳琳”年龄大的学号、姓名和年龄

模拟数据:学生表 s
snosnagemaj
s1张三18计算机
s2赵琳琳19数学
s3王敏20计算机
s4陈晨21英语
SQL
SELECT sno, sn, age FROM s WHERE age > ( SELECT age FROM s WHERE sn = '赵琳琳' );
1
先在 s 表中查出赵琳琳的 age
2
假设查到结果是 19
3
外层再找 age > 19 的学生
💬 人话翻译
这题不是直接比赵琳琳,而是先把“赵琳琳的年龄”提取出来,再让所有学生和这个年龄比较。
运行结果
snosnage
s3王敏20
s4陈晨21

查询与教师“顾伟”职称不同的教师号、姓名和职称

模拟数据:教师表 t
tnotnprofmajdeptsal
t1顾伟副教授计算机计算机学院5200
t2李明讲师数学数学学院5600
t3王华教授计算机计算机学院6800
t4刘芳讲师英语外国语学院7000
t5周强副教授计算机计算机学院6100
t6陈刚副教授化学化学学院6300
SQL
SELECT tno, tn, prof FROM t WHERE prof <> ( SELECT prof FROM t WHERE tn = '顾伟' );
理解提醒
<> 表示“不等于”。子查询先查出顾伟的职称,外层再筛选不同职称的教师。
运行结果
tnotnprof
t2李明讲师
t3王华教授
t4刘芳讲师

查询讲授“程序设计基础”课程的教师号、课程号和开课日期

模拟数据:课程表 c
cnocnct
c1程序设计基础64
c2数据库原理48
c4Web前端开发64
模拟数据:授课表 tc
tnocnotcdate
t1c12024-09-01
t1c22024-09-01
t2c42024-09-10
t3c12025-02-20
SQL
SELECT tno, cno, tcdate FROM tc WHERE cno = ( SELECT cno FROM c WHERE cn = '程序设计基础' );
容易卡住的点
课程名在 c 表里,授课信息在 tc 表里。题目想找的是授课信息,所以必须先从课程表查出课程号,再去授课表里找。
运行结果
tnocnotcdate
t1c12024-09-01
t3c12025-02-20

这一类题的统一思路

步骤理解思路
第 1 步先找题目中的“参照对象”,例如赵琳琳、顾伟、程序设计基础
第 2 步用子查询查出这个参照对象对应的一个具体值,例如年龄、职称、课程号
第 3 步让父查询拿着这个具体值去做比较或匹配

三、返回一组值的普通子查询

先把三个关键词记住

写法大意直观理解
IN在这组值里面像“名单里有没有他”
= ANY等于其中任意一个效果和 IN 很接近
> ANY只要大于其中某一个就行本质上等于大于最小值
> ALL必须大于全部本质上等于大于最大值
✅ 实战记忆
= ANY 基本可以记成“等于这个集合里的任意一个值”,所以在很多题里和 IN 作用相同。
💬 人话翻译
单值子查询像“问一个答案”,多值子查询像“拿到一份名单”。

查询学号为 s2 的学生选修的课程号、课程名和课时

模拟数据:课程表 c
cnocnct
c1程序设计基础64
c2数据库原理48
c3高等数学56
c4Web前端开发64
模拟数据:选课表 sc
snocnoscore
s1c186
s1c290
s2c188
s2c292
s3c385
s4c269
SQL
SELECT cno, cn, ct FROM c WHERE cno = ANY ( SELECT cno FROM sc WHERE sno = 's2' );
SQL
SELECT cno, cn, ct FROM c WHERE cno IN ( SELECT cno FROM sc WHERE sno = 's2' );
结论
这两题任务相同,= ANYIN 在这里作用相同,都表示“课程号属于 s2 选过的课程号集合”。
运行结果
cnocnct
c1程序设计基础64
c2数据库原理48

查询其他专业中比“计算机”专业某一教师工资高的教师

模拟数据:教师表 t
tnotnprofmajdeptsal
t1顾伟副教授计算机计算机学院5200
t2李明讲师数学数学学院5600
t3王华教授计算机计算机学院6800
t4刘芳讲师英语外国语学院7000
t5周强副教授计算机计算机学院6100
t6陈刚副教授化学化学学院6300
SQL
SELECT tno, tn, maj, sal FROM t WHERE (sal > ANY ( SELECT sal FROM t WHERE maj = '计算机' )) AND maj <> '计算机';
如何理解 > ANY
“比某一教师工资高”意思不是比所有人都高,只要比其中任意一个高就行。因此等价于:大于计算机专业教师工资中的最小值
运行结果
tnotnmajsal
t2李明数学5600
t4刘芳英语7000
t6陈刚化学6300
SQL
SELECT tno, tn, maj, sal FROM t WHERE sal > ( SELECT MIN(sal) FROM t WHERE maj = '计算机' ) AND maj <> '计算机';

查询选修课程号为 c1 的学号和姓名

模拟数据:学生表 s
snosnagemaj
s1张三18计算机
s2赵琳琳19数学
s3王敏20计算机
s4陈晨21英语
模拟数据:选课表 sc
snocnoscore
s1c186
s1c290
s2c188
s2c292
s3c385
s4c269
SQL
SELECT sno, sn FROM s WHERE sno IN ( SELECT sno FROM sc WHERE cno = 'c1' );
💬 人话翻译
先在选课表里查出谁选了 c1,再去学生表里把这些人的姓名取出来。
运行结果
snosn
s1张三
s2赵琳琳

查询其他专业中比“计算机”专业所有教师工资都高的教师

模拟数据:教师表 t
tnotnprofmajdeptsal
t1顾伟副教授计算机计算机学院5200
t2李明讲师数学数学学院5600
t3王华教授计算机计算机学院6800
t4刘芳讲师英语外国语学院7000
t5周强副教授计算机计算机学院6100
t6陈刚副教授化学化学学院6300
SQL
SELECT tno, tn, maj, sal FROM t WHERE (sal > ALL ( SELECT sal FROM t WHERE maj = '计算机' )) AND maj <> '计算机';
这一题和上题的区别一定要分清
> ANY 是只要比其中某一个高。
> ALL 是必须比所有人都高。
所以 sal > ALL(...) 可以理解为:工资必须大于这个集合中的最大值。
运行结果
tnotnmajsal
t4刘芳英语7000
SQL
SELECT tno, tn, maj, sal FROM t WHERE sal > ( SELECT MAX(sal) FROM t WHERE maj = '计算机' ) AND maj <> '计算机';

四、子查询不只用于 SELECT,也能用于数据操纵

教材里的第三类场景
子查询还可以出现在 INSERTUPDATE 这类数据操纵语句中。

把各学院教师的平均工资存入新表 avgsal

模拟数据:教师表 t
tnotnprofmajdeptsal
t1顾伟副教授计算机计算机学院5200
t2李明讲师数学数学学院5600
t3王华教授计算机计算机学院6800
t4刘芳讲师英语外国语学院7000
t5周强副教授计算机计算机学院6100
t6陈刚副教授化学化学学院6300
SQL
DROP TABLE IF EXISTS avgsal; CREATE TABLE avgsal ( department VARCHAR(20), average SMALLINT ); INSERT INTO avgsal SELECT dept, AVG(sal) FROM t GROUP BY dept;
理解提醒
这里教材标题写的是“利用子查询”,但从 SQL 写法看,核心其实是 INSERT INTO ... SELECT ...,也就是把查询结果直接插入新表。
插入后的 avgsal 表
departmentaverage
计算机学院6033
数学学院5600
外国语学院7000
化学学院6300

把教师号为 t1 的教师讲授课程的课时增加 16 学时

模拟数据:课程表 c
cnocnct
c1程序设计基础64
c2数据库原理48
c4Web前端开发64
模拟数据:授课表 tc
tnocnotcdate
t1c12024-09-01
t1c22024-09-01
t2c42024-09-10
t3c12025-02-20
SQL
UPDATE c SET ct = ct + 16 WHERE cno IN ( SELECT cno FROM tc WHERE tno = 't1' );
💬 人话翻译
先从授课表 tc 里查出 t1 教了哪些课,再回到课程表 c,把这些课的课时统一加 16。
更新后的课程表 c(只展示受影响记录)
cnocn原课时新课时
c1程序设计基础6480
c2数据库原理4864

把所有教师工资提高到平均工资的 1.2 倍

模拟数据:教师表 t
tnotnprofmajdeptsal
t1顾伟副教授计算机计算机学院5200
t2李明讲师数学数学学院5600
t3王华教授计算机计算机学院6800
t4刘芳讲师英语外国语学院7000
t5周强副教授计算机计算机学院6100
t6陈刚副教授化学化学学院6300
SQL
UPDATE t SET sal = ( SELECT 1.2 * AVG(sal) FROM t );
理解提醒
子查询只返回一个值:平均工资的 1.2 倍。然后把这个同一个值赋给每一位教师,所以最后所有教师工资会变成一样。
更新后的教师工资
说明
平均工资6166.67
1.2 × 平均工资7400
更新后每位教师工资7400

五、相关子查询:父查询换一行,子查询也要跟着重新判断

定义
如果子查询的条件中引用了父查询当前行中的字段值,那么这个子查询就是相关子查询。
理解重点
相关子查询最容易卡住的地方,不是语句长,而是执行方式和普通子查询不一样。它不是先把子查询一次算完,再统一比较;而是父查询每切到一行,子查询就会带着这一行里的字段值再查一次。
1
父查询先取当前这一行
2
子查询把这一行的字段代进去
3
判断这一行是否满足条件
4
再处理下一行

查询选修了课程号为 c1 的学号和姓名(使用相关子查询)

模拟数据:学生表 s
snosnagemaj
s1张三18计算机
s2赵琳琳19数学
s3王敏20计算机
s4陈晨21英语
模拟数据:选课表 sc
snocnoscore
s1c186
s1c290
s2c188
s2c292
s3c385
s4c269
SQL
SELECT sno, sn FROM s WHERE 'c1' IN ( SELECT cno FROM sc WHERE sno = s.sno );

动态演示:观察 s.sno 每次怎么变化

点不同学生,或点“自动演示”。页面会同步显示:父查询当前处理哪一行,子查询在 sc 表里实际查到了什么,以及这一行为什么保留。

当前父查询行 子查询匹配到当前学生的记录 同时满足当前学生且课程号为 c1
父查询正在看的学生表 s
子查询在选课表 sc 中的判断过程
当前学生: 子查询返回: 条件判断: 本行去留:
这一轮子查询真正执行的是

                
目前已经进入结果集的学生
为什么它是相关子查询
因为子查询里的 sno = s.sno 用到了父查询当前这一行的 s.sno。当前行如果是 s1,子查询就查 sno='s1';当前行如果换成 s2,子查询就改查 sno='s2'
最终运行结果
snosn
s1张三
s2赵琳琳

查询没有选修课程号为 c1 的学号和姓名(使用相关子查询)

模拟数据:学生表 s
snosnagemaj
s1张三18计算机
s2赵琳琳19数学
s3王敏20计算机
s4陈晨21英语
模拟数据:选课表 sc
snocnoscore
s1c186
s1c290
s2c188
s2c292
s3c385
s4c269
SQL
SELECT sno, sn FROM s WHERE 'c1' NOT IN ( SELECT cno FROM sc WHERE sno = s.sno );

动态演示:把 IN 换成 NOT IN 之后发生了什么

仍然是逐行判断,只是条件变成:子查询返回的课程号列表里不能出现 c1。点一遍学生,很容易看出为什么最后留下的是 s3s4

父查询正在看的学生表 s
子查询在选课表 sc 中的判断过程
当前学生: 子查询返回: 条件判断: 本行去留:
这一轮子查询真正执行的是

                
目前已经进入结果集的学生
直观理解
外层每拿到一个学生,就到他的选课记录里看看有没有 c1。如果列表里没有 c1,这一行才会被保留。
最终运行结果
snosn
s3王敏
s4陈晨

六、EXISTS / NOT EXISTS:不看返回什么值,只看“有没有记录”

核心理解
EXISTS 关心的不是子查询具体返回哪一列,而是子查询结果集是否为空。
有记录,就为真。没有记录,就为假。

查询选修了课程号为 c1 的学号和姓名(使用 EXISTS)

模拟数据:学生表 s
snosnagemaj
s1张三18计算机
s2赵琳琳19数学
s3王敏20计算机
s4陈晨21英语
模拟数据:选课表 sc
snocnoscore
s1c186
s1c290
s2c188
s2c292
s3c385
s4c269
SQL
SELECT sno, sn FROM s WHERE EXISTS ( SELECT * FROM sc WHERE sno = s.sno AND cno = 'c1' );
理解方式
对每个学生来说,只要在 sc 表里找到一条“这个学生选了 c1”的记录,EXISTS 就成立,这个学生就会被选出来。
运行结果
snosn
s1张三
s2赵琳琳

查询没有选修课程号为 c1 的学号和姓名(使用 NOT EXISTS)

模拟数据:学生表 s
snosnagemaj
s1张三18计算机
s2赵琳琳19数学
s3王敏20计算机
s4陈晨21英语
模拟数据:选课表 sc
snocnoscore
s1c186
s1c290
s2c188
s2c292
s3c385
s4c269
SQL
SELECT sno, sn FROM s WHERE NOT EXISTS ( SELECT * FROM sc WHERE sno = s.sno AND cno = 'c1' );
💬 人话翻译
只要找不到“该学生选了 c1”的记录,NOT EXISTS 就为真。
运行结果
snosn
s3王敏
s4陈晨

查询教师号为 t2 的教师讲授的课程号、课程名和课时

模拟数据:课程表 c
cnocnct
c1程序设计基础64
c2数据库原理48
c4Web前端开发64
模拟数据:授课表 tc
tnocnotcdate
t1c12024-09-01
t1c22024-09-01
t2c42024-09-10
t3c12025-02-20
SQL
SELECT cno, cn, ct FROM c WHERE EXISTS ( SELECT * FROM tc WHERE cno = c.cno AND tno = 't2' );
为什么外层是课程表 c
因为最终想输出的是课程号、课程名和课时,这些字段都在课程表 c 中。EXISTS 只是帮我们判断:当前这门课是不是 t2 教过的。
运行结果
cnocnct
c4Web前端开发64

七、练习

练习 1

下面哪一句最能说明“相关子查询”的特点?

A. 子查询一定只返回一个值
B. 子查询会用到父查询当前行中的字段值
C. 子查询只能写在 SELECT 后面
D. 子查询执行完后一定不会再执行
正确答案是 B。相关子查询的本质,是子查询条件依赖父查询当前处理的这一行,所以会逐行执行。
练习 2

如果子查询返回的是一组课程号,外层常用哪个运算符判断“某课程号是否在这组结果中”?

A. <>
B. >
C. IN
D. EXISTS
正确答案是 C。IN 最适合“属于某个集合”这种判断。
练习 3

sal > ALL (子查询) 最接近下面哪种理解?

A. 只要大于其中一个值
B. 等于其中任意一个值
C. 在这个集合里面
D. 大于这个集合中的最大值
正确答案是 D。要比集合中的所有值都大,本质上就必须大于这个集合的最大值。